#include "console.h"
#include "types.h"

using namespace Types;

#include "btparser/btparser/lexer.h"

void LoadModel(const std::string& owner, Model& model);

bool ParseTypes(const std::string& parse, const std::string& owner) {
  Lexer lexer;
  lexer.SetInputData(parse);
  std::vector<Lexer::TokenState> tokens;
  size_t index = 0;
  auto getToken = [&](size_t i) -> Lexer::TokenState& {
    if (index >= tokens.size() - 1) i = tokens.size() - 1;
    return tokens[i];
  };
  auto curToken = [&]() -> Lexer::TokenState& { return getToken(index); };
  auto isToken = [&](Lexer::Token token) {
    return getToken(index).Token == token;
  };
  auto isTokenList = [&](std::initializer_list<Lexer::Token> il) {
    size_t i = 0;
    for (auto l : il)
      if (getToken(index + i++).Token != l) return false;
    return true;
  };
  std::string error;
  if (!lexer.DoLexing(tokens, error)) {
    dputs_untranslated(error.c_str());
    return false;
  }
  Model model;

  auto errLine = [&]() {
    dprintf("[line %d:%d] ", curToken().CurLine, curToken().LineIndex);
  };
  auto eatSemic = [&]() {
    while (curToken().Token == Lexer::tok_semic) index++;
  };
  auto parseTypedef = [&]() {
    if (isToken(Lexer::tok_typedef)) {
      index++;
      std::vector<Lexer::TokenState> tdefToks;
      while (!isToken(Lexer::tok_semic)) {
        if (isToken(Lexer::tok_eof)) {
          errLine();
          dputs("unexpected eof in typedef");
          return false;
        }
        tdefToks.push_back(curToken());
        index++;
      }
      eatSemic();
      if (tdefToks.size() >= 2)  // at least typedef a b;
      {
        Member tm;
        tm.name = lexer.TokString(tdefToks[tdefToks.size() - 1]);
        tdefToks.pop_back();
        for (auto& t : tdefToks)
          if (!t.IsType() && t.Token != Lexer::tok_op_mul &&
              t.Token != Lexer::tok_identifier && t.Token != Lexer::tok_void) {
            errLine();
            dprintf("token %s is not a type...\n", lexer.TokString(t).c_str());
            return false;
          } else {
            if (!tm.type.empty() && t.Token != Lexer::tok_op_mul)
              tm.type.push_back(' ');
            tm.type += lexer.TokString(t);
          }
        // dprintf("typedef %s:%s\n", tm.type.c_str(), tm.name.c_str());
        model.types.push_back(tm);
        return true;
      }
      errLine();
      dputs("not enough tokens for typedef");
      return false;
    }
    return true;
  };
  auto parseMember = [&](StructUnion& su) {
    std::vector<Lexer::TokenState> memToks;
    while (!isToken(Lexer::tok_semic)) {
      if (isToken(Lexer::tok_eof)) {
        errLine();
        dputs("unexpected eof in member");
        return false;
      }
      memToks.push_back(curToken());
      index++;
    }
    eatSemic();
    if (memToks.size() >= 2)  // at least type name;
    {
      Member m;
      for (size_t i = 0; i < memToks.size(); i++) {
        const auto& t = memToks[i];
        if (t.Token == Lexer::tok_subopen) {
          if (i + 1 >= memToks.size()) {
            errLine();
            dputs("unexpected end after [");
            return false;
          }
          if (memToks[i + 1].Token != Lexer::tok_number) {
            errLine();
            dputs("expected number token");
            return false;
          }
          m.arrsize = int(memToks[i + 1].NumberVal);
          if (i + 2 >= memToks.size()) {
            errLine();
            dputs("unexpected end, expected ]");
            return false;
          }
          if (memToks[i + 2].Token != Lexer::tok_subclose) {
            errLine();
            dprintf("expected ], got %s\n",
                    lexer.TokString(memToks[i + 2]).c_str());
            return false;
          }
          if (i + 2 != memToks.size() - 1) {
            errLine();
            dputs("too many tokens");
            return false;
          }
          break;
        } else if (i + 1 == memToks.size() ||
                   memToks[i + 1].Token == Lexer::tok_subopen)  // last = name
        {
          m.name = lexer.TokString(memToks[i]);
        } else if (!t.IsType() && t.Token != Lexer::tok_op_mul &&
                   t.Token != Lexer::tok_identifier &&
                   t.Token != Lexer::tok_void) {
          errLine();
          dprintf("token %s is not a type...\n", lexer.TokString(t).c_str());
          return false;
        } else {
          if (!m.type.empty() && t.Token != Lexer::tok_op_mul)
            m.type.push_back(' ');
          m.type += lexer.TokString(t);
        }
      }
      // dprintf("member: %s %s;\n", m.type.c_str(), m.name.c_str());
      su.members.push_back(m);
      return true;
    }
    errLine();
    dputs("not enough tokens for member");
    return false;
  };
  auto parseStructUnion = [&]() {
    if (isToken(Lexer::tok_struct) || isToken(Lexer::tok_union)) {
      StructUnion su;
      su.isunion = isToken(Lexer::tok_union);
      index++;
      if (isTokenList({Lexer::tok_identifier, Lexer::tok_bropen})) {
        su.name = lexer.TokString(curToken());
        index += 2;
        while (!isToken(Lexer::tok_brclose)) {
          if (isToken(Lexer::tok_eof)) {
            errLine();
            dprintf("unexpected eof in %s\n", su.isunion ? "union" : "struct");
            return false;
          }
          if (isToken(Lexer::tok_bropen)) {
            errLine();
            dputs("nested blocks are not allowed!");
            return false;
          }
          if (!parseMember(su)) return false;
        }
        index++;  // eat tok_brclose
        // dprintf("%s %s, members: %d\n", su.isunion ? "union" : "struct",
        // su.name.c_str(), int(su.members.size()));
        model.structUnions.push_back(su);
        if (!isToken(Lexer::tok_semic)) {
          errLine();
          dputs("expected semicolon!");
          return false;
        }
        eatSemic();
        return true;
      } else {
        errLine();
        dputs("invalid struct token sequence!");
        return false;
      }
    }
    return true;
  };

  while (!isToken(Lexer::tok_eof)) {
    auto curIndex = index;
    if (!parseTypedef()) return false;
    if (!parseStructUnion()) return false;
    eatSemic();
    if (curIndex == index) {
      errLine();
      dprintf("unexpected token %s\n", lexer.TokString(curToken()).c_str());
      return false;
    }
  }

  LoadModel(owner, model);

  return true;
}